#include "AttrAnalyzer.h"

#include <clang/AST/AST.h>
#include <clang/AST/Decl.h>
#include <clang/Basic/SourceManagerInternals.h>

#include <boost/filesystem.hpp>
#include <iostream>

using namespace std;
using namespace clang;
namespace fs = boost::filesystem;

bool rocket::common::isLocal(const Decl *decl) {
  if (decl) {
    try {
      if (!decl->getLocation().isValid()) {
#ifdef DEBUG
        // TODO: Implicit struct __va_list_tag
        cerr << "Non-location decl:" << endl;
        decl->dump();
#endif
        return false;
      }

      auto &SM = decl->getASTContext().getSourceManager();
      auto file_loc = SM.getPresumedLoc(decl->getLocation());
      if (!file_loc.isValid()) {
        return false;
      }

      //      std::string filename =
      //          fs::canonical(fs::path(file_loc.getFilename())).string();
      //      std::string current_path = fs::current_path().string();
      //      //      cout << filename << endl << current_path << endl << endl;
      //      if (filename.length() <= current_path.length() ||
      //          filename.compare(0, current_path.length() + 1, current_path +
      //          "/") !=
      //              0) {
      //        return false;
      //      }
      if (isInSystemspace(decl)) {
        return false;
      }

    } catch (fs::filesystem_error &e) {
      // TODO: non-exist file
    }
    return true;
  } else {
    return false;
  }
}

bool rocket::common::isLocalGlobal(const VarDecl *vd) {
  if (vd && (!vd->isLocalVarDeclOrParm() ||
             (vd->getPreviousDecl() &&
              !vd->getPreviousDecl()->isLocalVarDeclOrParm()) ||
             vd->isExternC())) {
    return isLocal(vd);
  } else {
    return false;
  }
}

bool rocket::common::isAnonymous(const TagDecl *decl) {
  assert(decl != NULL);
  return !decl->getIdentifier() && !decl->getTypedefNameForAnonDecl();
}

bool rocket::common::isAnonymous(const QualType &type) {
  clang::TagDecl *decl = type->getAsTagDecl();
  return (decl && isAnonymous(decl));
}

bool rocket::common::isAnonymous(const string &type) {
  return type.find("(anonymous at") != type.npos;
}

bool rocket::common::isCompleteType(const QualType &type) {
  clang::TagDecl *decl = type->getAsTagDecl();
  return !decl || isa<clang::ClassTemplateSpecializationDecl>(decl) ||
         decl->getDefinition();
}

bool rocket::common::areSameType(const QualType &lht, const QualType &rht,
                                 const ASTContext &ast) {
  // TODO: 是否存在借助ast的类型比较
  return getFullQTypeName(lht.getCanonicalType(), ast) ==
         getFullQTypeName(rht.getCanonicalType(), ast);
}

bool rocket::common::isInStd(const Decl *decl) {
  return decl->isInStdNamespace();
}

bool rocket::common::isInBoost(const Decl *decl) {
  return isInNamespace("boost", decl);
}

bool rocket::common::isInNamespace(const string &name, const Decl *decl) {
  const DeclContext *DC = decl->getDeclContext();
  while (DC) {
    if (isNamespace(name, DC)) return true;
    DC = DC->getParent();
  }
  return false;
}

bool rocket::common::isNamespace(const string &name, const DeclContext *DC) {
  if (!DC || !DC->isNamespace()) return false;

  const auto *ND = cast<NamespaceDecl>(DC);
  if (ND->isInline()) {
    return isNamespace(name, ND->getParent());
  }

  if (!DC->getParent()->getRedeclContext()->isTranslationUnit()) return false;

  const IdentifierInfo *II = ND->getIdentifier();
  return II && II->isStr(name);
}

bool rocket::common::isContextSpecDecl(const Decl *d) {
  const DeclContext *Ctx = d->getDeclContext();

  using ContextsTy = SmallVector<const DeclContext *, 8>;
  ContextsTy Contexts;

  // Collect named contexts.
  while (Ctx) {
    if (isa<NamedDecl>(Ctx)) Contexts.push_back(Ctx);
    // FIXME:不止据名声明
    Ctx = Ctx->getParent();
  }

  for (const DeclContext *DC : llvm::reverse(Contexts)) {
    // FIXME:不止类特化
    if (const auto *RSpec = dyn_cast<ClassTemplateSpecializationDecl>(DC)) {
      return true;
    }
  }

  return false;
}

bool rocket::common::isInSystemspace(const Decl *decl) {
  auto &sm = decl->getASTContext().getSourceManager();
  SourceLocation loc = decl->getLocation();
  FileID file_id;
  unsigned offset;
  std::tie(file_id, offset) = sm.getDecomposedExpansionLoc(loc);
  bool invalid;
  const SrcMgr::SLocEntry &entry = sm.getSLocEntry(file_id, &invalid);
  if (!invalid) {
    const SrcMgr::FileInfo &file_info = entry.getFile();
    if (file_info.hasLineDirectives()) {
      if (auto entry =
              sm.getLineTable().FindNearestLineEntry(file_id, offset)) {
        const SrcMgr::CharacteristicKind &file_type = entry->FileKind;
        if (file_type != SrcMgr::C_User &&
            file_type != SrcMgr::C_User_ModuleMap) {
          return true;
        }
      }
    }
    SrcMgr::CharacteristicKind file_type =
        entry.getFile().getFileCharacteristic();
    if (file_type != SrcMgr::C_User && file_type != SrcMgr::C_User_ModuleMap) {
      return true;
    }
  }

  return false;
}

bool rocket::common::isTypeDefinedBefore(const Decl *decl, const Type *type) {
  if (!decl || !type) return false;

  while (auto typedef_type = dyn_cast<clang::TypedefType>(type)) {
    type = typedef_type->desugar().getTypePtr();
  }

  auto tag_decl = type->getAsTagDecl();
  if (!tag_decl) return true;

  auto tag_def = tag_decl->getDefinition();
  if (!tag_def) {
    if (isa<ClassTemplateSpecializationDecl>(tag_decl))
      return true;
    else
      return false;
  }

  bool ret = decl->getASTContext().getSourceManager().isBeforeInTranslationUnit(
      tag_def->getBeginLoc(), decl->getBeginLoc());

  return ret;
}

bool rocket::common::isDefinedType(const Type *type) {
  while (auto typedef_type = dyn_cast<clang::TypedefType>(type)) {
    type = typedef_type->desugar().getTypePtr();
  }

  auto tag_decl = type->getAsTagDecl();
  if (!tag_decl) return true;

  auto tag_def = tag_decl->getDefinition();
  if (!tag_def) {
    if (isa<ClassTemplateSpecializationDecl>(tag_decl))
      return true;
    else
      return false;
  } else {
    return false;
  }
}
